﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NHibernate.Audit.Cfg;
using NHibernate.Cfg;
using NHibernate.Mapping;

namespace NHibernate.Audit.Mapping
{
    public class AuditMapper
    {

        public AuditMapper(IMappingsFacade mappingsFacade, 
            ITableMapper tableMapper)
        {
            this.mappingsFacade = mappingsFacade;
            this.tableMapper = tableMapper;
        }

        private IMappingsFacade mappingsFacade;
        private ITableMapper tableMapper;


        public void AddAuditing(IEnumerable<AuditClassMetadata> classesMetadata)
        {
            foreach (var classMeta in classesMetadata)
                AddAuditing(classMeta);
        }

        public void AddAuditing(AuditClassMetadata classMetadata)
        {
            var realClass = classMetadata.RealClass;

            if (null == realClass)
                throw new NullReferenceException("classMetadata.RealClass can't be null");

            var tableMetadata = classMetadata.TableMetadata;

            if (null == tableMetadata)
                throw new NullReferenceException("classMetadata.Table can't be null");

            System.Type realType = System.Type.GetType(realClass.ClassName);
            System.Type revisionType = System.Type.GetType(classMetadata.AuditClassName); 

            var auditClass = new RootClass()
            {
                BatchSize = realClass.BatchSize,
                CacheConcurrencyStrategy = realClass.CacheConcurrencyStrategy,
                ClassName = realClass.ClassName,
                EntityName = classMetadata.AuditEntityName,
                IsAbstract = realClass.IsAbstract,
                IsLazy = realClass.IsLazy,
                IsMutable = false,
                ProxyInterfaceName = realClass.ProxyInterfaceName
            };

            ((ITableOwner)auditClass).Table = tableMapper.BuildAuditTable(tableMetadata, mappingsFacade);

            AddProperties(auditClass, classMetadata); ;

            BuildKey(classMetadata, auditClass, realClass);

            mappingsFacade.CreatePrimaryKeyFor(auditClass);
            mappingsFacade.AddClass(auditClass);

        }


        private void AddProperties(PersistentClass AuditClass, AuditClassMetadata ClassMetadata)
        {
            foreach (var property in ClassMetadata.Properties)
                AddProperty(AuditClass, property);
        }

        private void AddProperty(PersistentClass AuditClass, AuditPropertyMetadata PropertyMetadata)
        {
            var realProperty = PropertyMetadata.Property;
            var realValue = realProperty.Value;
            var auditTable = AuditClass.Table;
            var auditValue = new SimpleValue(AuditClass.Table);
            auditValue.TypeName = realValue.Type.Name;
            var columns = realValue.ColumnIterator.Cast<Column>();
            foreach (var column in columns)
            {
                Column auditColumn = auditTable.GetColumn(column.Name);
                auditValue.AddColumn(auditColumn);
            }
            var auditProperty = new Property(auditValue);
            auditProperty.Name = realProperty.Name;
            AuditClass.AddProperty(auditProperty);
        }

        private void BuildKey(AuditClassMetadata classMeta, PersistentClass auditClass, PersistentClass realClass)
        {
            var id = new Component(auditClass);
            id.ComponentClass = id.Owner.MappedClass;
            id.IsEmbedded = true;
            auditClass.Identifier = id;
            auditClass.HasEmbeddedIdentifier = true; 

            // Add RealClass's key
            AddToComposite(id, realClass.IdentifierProperty, auditClass);

            // Add Revision # to key
            AddToComposite(id, classMeta.RevisionProperty, auditClass);
        }

        private void AddToComposite(Component id, Property realProp, PersistentClass auditClass)
        {
            var value = new SimpleValue(auditClass.Table);
            string name = realProp.Name;
            value.TypeName = realProp.Type.Name;
            var columns = realProp.ColumnIterator.Cast<Column>();
            foreach (var column in columns)
            {
                value.AddColumn(column);
            }
            var property = new Property(value);
            property.Name = name;
            id.AddProperty(property);
            // auditClass.AddProperty(property);
        }

    }
}
